// SPDX-FileCopyrightText: Copyright 2023 citrus Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <sys/prctl.h>
#include <unistd.h>
#include <cstdint>

#include <string>
#include <list>
#include <android/hardware_buffer.h>
#include <android/binder_parcel.h>
#include <android/app/src/main/jni/emu_window/backend_window.h>
#include "binder.h"
#include "android_protocol.h"
#include "common/logging/log.h"
#include "input_common/drivers/android.h"

#define AIBinder_prepareTransaction(binder, in) (*(in) ? AParcel_reset(*(in)) : (*(in) = AParcel_create(), STATUS_OK))

static binder_status_t AIBinder_transact(AIBinder *binder, transaction_code_t code, AParcel **in, AParcel **out, int);

#include <aidl/com/fanwj/citrus/aidl/IBackend.cpp>
#include <aidl/com/fanwj/citrus/aidl/IFrontend.cpp>
#include <sys/eventfd.h>
#include <sys/mman.h>

using namespace aidl::com::fanwj::citrus::aidl;

static int hardwareBufferFd;
static int vibratorFd;

static int socketpair(int fds[2]) {
    return socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
}

static int createFileDescriptor(int sendFd, int(*func)(int*) = &socketpair) {
    int fds[2];
    if(func(fds)) return -1;
    sendFileDescriptor(sendFd, fds[0]);
    close(fds[0]);
    return fds[1];
}

static binder_status_t AIBinder_transact(AIBinder *_aidl_binder, transaction_code_t _aidl_code, AParcel **in, AParcel **out, int) {
    struct socket_t final {
        inline socket_t() : fd(createFileDescriptor(fileDescriptorFd)) {
            if (fd < 0) exit(fd);
        }
        inline ~socket_t() {
            const uint64_t value = 0LLU;
            send(fd, &value, sizeof(value), 0);
            close(fd);
        }
        const int fd;
    } static thread_local const sock;
    const int fd = sock.fd;
    int code = static_cast<int>(_aidl_code);
    auto data = *in;
    auto reply = (*out) ? (AParcel_reset(*out), *out) : *out = AParcel_create();
    for (;;) {
        sendMessage(fd, code, data);
        code = recvMessage(fd, reply);
        if (code < 0) return STATUS_OK;
        else if (code == 0) return STATUS_TIMED_OUT;
        AParcel_reset(data);
        _aidl_com_fanwj_citrus_aidl_IBackend_onTransact(_aidl_binder, code, reply,data);
        code = -code;
    }
}

Common::Input::DriverResult InputCommon::Android::SetVibration(
        const PadIdentifier& identifier,
        const Common::Input::VibrationStatus& vibration) {
    uint16_t value[4] = {
            (uint16_t)vibration.low_frequency,
            (uint16_t)vibration.high_frequency,
            (uint16_t)(vibration.low_amplitude * 0xFF),
            (uint16_t)(vibration.high_amplitude * 0xFF)
    };
    write(vibratorFd, value, sizeof(value));
    return Common::Input::DriverResult::Success;
}

void BackendWindow::OnImageUpdate(AHardwareBuffer *buffer) {
    AHardwareBuffer_sendHandleToUnixSocket(buffer, hardwareBufferFd);
}

Binder::Binder(AIBinder_Class *clazz) : frontend(::ndk::SpAIBinder(AIBinder_new(clazz, this))) {
}

::ndk::SpAIBinder Binder::createBinder() {
    return frontend.asBinder();
}

::ndk::ScopedAStatus Binder::NativeLibrary_syncFileDescriptor(int64_t addr) {
    *(int*)addr = recvFileDescriptor(fileDescriptorFd);
    return ::ndk::ScopedAStatus::ok();
}

std::shared_ptr<Binder> Binder::instance;
extern void* virtual_base;

int main(int argc, char *argv[]) {
    int ret = 0;
    prctl(PR_SET_PDEATHSIG, SIGKILL);
    signal(SIGPIPE, SIG_IGN);
    if (MAP_FAILED == virtual_base)
        return -1;
    if (fcntl(fileDescriptorFd, F_GETFD) < 0)
        return -2;
    if ((hardwareBufferFd = createFileDescriptor(fileDescriptorFd)) < 0)
        return hardwareBufferFd;
    if ((vibratorFd = createFileDescriptor(fileDescriptorFd, &pipe)) < 0)
        return vibratorFd;
    AIBinder_Class *clazz = ::ndk::ICInterface::defineClass(IBackend::descriptor,[](AIBinder*, transaction_code_t, const AParcel*, AParcel*) -> binder_status_t {
        exit(-3);
        return STATUS_UNKNOWN_TRANSACTION;
    });
    Binder::instance = ::ndk::SharedRefBase::make<Binder>(clazz);
    Binder::getInstanceFrontend()->NativeLibrary_startMainLooper(&ret);
    return ret;
}
